Setup & Configuration
- The code below will load the libraries you will need for this tutorial.
options(width = 100)
library(tibble) # special type of data frame
library(magrittr) # pipes
suppressMessages(library(dplyr)) # data manipulation
library(nycflights13) # toy data set
suppressMessages(library(lubridate)) # working with dates/times
library(ggplot2) # pretty plots
library(reshape2) # melt/reshape data frames
- The data we will be using today is available via the
nycflights13 package.
- R environment:
- Now is a good time to check that your version of R is up-to-date. If you are not running version 3.2.3 or later it is time to update. We will use a function from the
devtools package to check your session info:
devtools::session_info()
Pipes (magrittr)

This is an idea that has been around for a long time, particularly in the Linux/Unix ecosystem, where it is denoted with the “|” symbol:
- Complex tasks can be accomplished by stringing together many simple tools - syntax is important for efficiency / readability
- The idea of pipes is that the result of the previous command is passed to the next command, which allows many tools to be strung together - from left to right.
- In R,
magrittr introduces the %>% pipe operator which runs the expression of the left and passes the result as the first argument to expression on the right.
You can think about the following sequence of actions:
1. find key
2. unlock car
3. start car
4. drive to school
5. park.
Expressed as a set of nested functions in R pseudocode this would look like:
park(drive(start_car(find("keys")), to="campus"))
Writing it out using pipes give it a more natural (and easier to read!) structure:
find("keys") %>%
start_car() %>%
drive(to="campus") %>%
park()
Approaches
All of the following are fine, it comes down to personal preference (and IMHO, readability):
# Nested:
h( g( f(x), y = 1), z = 1 )
# Piped:
f(x) %>% g(y = 1) %>% h(z = 1)
# Intermediate:
res <- f(x)
res <- g(res, y = 1)
res <- h(res, z = 1)
What about other arguments?
Sometimes we want to send our results to an function argument other than first one or we want to use the previous result for multiple arguments. In these cases we can refer to the previous result using “.”.
data.frame(a = 1:3, b = 3:1) %>% lm(a ~ b, data = .)
Call:
lm(formula = a ~ b, data = .)
Coefficients:
(Intercept) b
4 -1
data.frame(a = 1:3, b = 3:1) %>% .[[1]]
[1] 1 2 3
data.frame(a = 1:3, b = 3:1) %>% .[[length(.)]]
[1] 3 2 1
Tibbles
Modern data frames
Hadley Wickham has a package that modifies data frames to be more modern, or as he calls them surly and lazy. That is they don’t change variable names or types, and don’t do partial matching; and they complain more (e.g. when a variable does not exist). This forces you to confront problems earlier, typically leading to cleaner, more expressive code. Tibbles also have an enhanced print() method which makes them easier to use with large datasets containing complex objects.
class(iris)
[1] "data.frame"
tbl_iris <- as.tibble(iris)
class(tbl_iris)
[1] "tbl_df" "tbl" "data.frame"
Lazy tibbles
tbl_iris[1, ]
tbl_iris[, "Species"]
Lazy tibbles (factors)
data_frame(x = 1:3, y = c("A", "B", "C"))
Surly Tibbles
tbl_iris[, "Name"] # errors out; invalid index
Error: Column `Name` not found
tbl_iris[["Name"]] # same as above; different syntax
NULL
iris$Name # returns NULL
NULL
tbl_iris$Name # returns NULL (with explanation!)
Unknown or uninitialised column: 'Name'.
NULL
tbl_iris[160, ] # exceeded index; returns 1 row data frame of NAs
dplyr 
A Grammar of Data Manipulation
The dplyr package is based on the concepts of functions as verbs that manipulate data frames.
Single data frame functions / verbs:
tbl_df(): add the tbl_df class
filter(): pick rows matching criteria
slice(): pick rows using index(es)
select(): pick columns by name
pull(): grab a column as a vector
rename(): rename specific columns
arrange(): reorder rows
mutate(): add new variables
transmute(): create new data frame with variables
distinct(): filter for unique rows
sample_n() / sample_frac(): randomly sample rows
summarise(): reduce variables to values
- … and many more
dplyr rules for functions
1. First argument is always a data frame
2. Subsequent arguments say what to do with that data frame
3. Always return a data frame
4. Do not modify in place
5. Performance via lazy evaluation
Example Data
We will demonstrate dplyr functionality using the nycflights13 data which contains data on flights departing from New York City. It was written by Hadley Wickham and includes airline on-time data for all flights departing NYC in 2013 as well as useful metadata on airlines, airports, weather, and planes (see ?nycflights13).
Structure of flights
flights %<>% as.tibble # 'rebound' pipe; aka compound assignment
str(flights)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame': 336776 obs. of 19 variables:
$ year : int 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 ...
$ month : int 1 1 1 1 1 1 1 1 1 1 ...
$ day : int 1 1 1 1 1 1 1 1 1 1 ...
$ dep_time : int 517 533 542 544 554 554 555 557 557 558 ...
$ sched_dep_time: int 515 529 540 545 600 558 600 600 600 600 ...
$ dep_delay : num 2 4 2 -1 -6 -4 -5 -3 -3 -2 ...
$ arr_time : int 830 850 923 1004 812 740 913 709 838 753 ...
$ sched_arr_time: int 819 830 850 1022 837 728 854 723 846 745 ...
$ arr_delay : num 11 20 33 -18 -25 12 19 -14 -8 8 ...
$ carrier : chr "UA" "UA" "AA" "B6" ...
$ flight : int 1545 1714 1141 725 461 1696 507 5708 79 301 ...
$ tailnum : chr "N14228" "N24211" "N619AA" "N804JB" ...
$ origin : chr "EWR" "LGA" "JFK" "JFK" ...
$ dest : chr "IAH" "IAH" "MIA" "BQN" ...
$ air_time : num 227 227 160 183 116 150 158 53 140 138 ...
$ distance : num 1400 1416 1089 1576 762 ...
$ hour : num 5 5 5 5 6 5 6 6 6 6 ...
$ minute : num 15 29 40 45 0 58 0 0 0 0 ...
$ time_hour : POSIXct, format: "2013-01-01 05:00:00" "2013-01-01 05:00:00" "2013-01-01 05:00:00" ...
Look at full “tibble”" data frame
flights
dplyr Verbs
March flights dplyr::filter
flights %>% filter(month == 3)
First week of March flights dplyr::filter
flights %>% filter(month == 3, day <= 7)
Flights to LAX or RDU in March dplyr::filter
flights %>% filter(dest == "LAX" | dest == "RDU", month==3)
First 10 flights dplyr::slice
flights %>% slice(1:10)
Last 5 flights dplyr::slice
flights %>% slice((n() - 5):n())
Select columns dplyr::select
flights %>% select(year, month, day)
Exclude columns dplyr::select
flights %>% select(-year, -month, -day)
Select ranges dplyr::select
flights %>% select(year:day)
Exclude ranges dplyr::select
flights %>% select(-(year:day))
Select matching dplyr::select
flights %>% select(contains("dep"),
contains("arr"))
flights %>% select(starts_with("dep"),
starts_with("arr"))
Other helpers:
current_vars
starts_with
ends_with
contains
matches
num_range
one_of
everything
Using dplyr::pull
names(flights)
[1] "year" "month" "day" "dep_time" "sched_dep_time"
[6] "dep_delay" "arr_time" "sched_arr_time" "arr_delay" "carrier"
[11] "flight" "tailnum" "origin" "dest" "air_time"
[16] "distance" "hour" "minute" "time_hour"
flights %>% pull("year") %>% head()
[1] 2013 2013 2013 2013 2013 2013
flights %>% pull(1) %>% head()
[1] 2013 2013 2013 2013 2013 2013
flights %>% pull(-1) %>% head()
[1] "2013-01-01 05:00:00 UTC" "2013-01-01 05:00:00 UTC" "2013-01-01 05:00:00 UTC"
[4] "2013-01-01 05:00:00 UTC" "2013-01-01 06:00:00 UTC" "2013-01-01 05:00:00 UTC"
Change column names dplyr::rename
flights %>% rename(tail_number = tailnum)
select() vs. rename()
flights %>% select(tail_number = tailnum, -tailnum)
flights %>% select(-tailnum, tail_number = tailnum)
Sort data dplyr::arrange
flights %>% filter(month == 3, day == 2) %>% arrange(origin, dest)
Descending order dplyr::arrange
flights %>% filter(month == 3, day == 2) %>%
arrange(desc(origin), dest) %>% select(origin, dest, tailnum)
Modify columns dplyr::mutate
flights %>%
select(1:3) %>%
mutate(date = paste(month, day, year, sep="/") %>% mdy())
Create new tibble from existing columns dplyr::transmute
flights %>%
select(1:3) %>%
transmute(date = paste(month, day, year, sep="/") %>% mdy())
Find unique rows dplyr::distinct
flights %>%
select(origin, dest) %>%
distinct() %>%
arrange(origin, dest)
Sample rows dplyr::sample_n
flights %>% sample_n(10)
Sample rows dplyr::sample_frac
flights %>% sample_frac(0.001)
Summary via dplyr::summarise
flights %>% mutate(date = paste(month, day, year, sep = "/") %>% mdy()) %>%
summarize(n(), min(date), max(date))
Set Groupings dplyr::group_by
flights %>% group_by(origin)
Summarize Groups dplyr::summarise dplyr::group_by
flights %>%
group_by(origin) %>%
mutate(date = paste(month, day, year, sep="/") %>% mdy()) %>%
summarize(n(), min(date), max(date))
Plotting
No data wrangling and/or data exploration is complete without at least some visual exploration of the data. Asking yourself: what do I have here? how is my data (particularly my class variable or response variable) distributed? Are there any missing values? Negative values?
par(mfrow = c(2, 2)) # make 2x2 grid for plots
par(mgp = c(2, 0.75, 0), mar = c(3, 4, 3, 1)) # graphics settings; squeeze margins
lapply(names(iris)[-5], function(class)
boxplot(split(iris[[class]], iris$Species), main = class, col = 1:3)) %>%
invisible # pipe to invisible to suppress output

The same via 
thm <- theme_bw() +
theme(
panel.background = element_rect(fill = "transparent", colour = NA),
plot.background = element_rect(fill = "transparent", colour = NA),
legend.position = "top",
legend.background = element_rect(fill = "transparent", colour = NA),
legend.key = element_rect(fill = "transparent", colour = NA)
)
theme_set(thm)
df <- reshape2::melt(iris, id.vars = "Species", variable.name = "Feature",
value.name = "cm") %>%
mutate(Feature=gsub("\\.", " ", Feature))
ggplot(df, aes(y = cm, x = Species, fill = Species)) +
geom_boxplot(color = "#1F3552", alpha = 0.75, size = 0.5) +
scale_x_discrete(name = "Species") +
ggtitle("Overall Title") +
theme(plot.title = element_text(hjust = 0.5)) +
facet_wrap(~Feature, ncol = 2)

Pairwise plots
Very useful for visually inspecting variables for patterns of interest. You will typically need to know your outcome of interest a priori, i.e. supervised analysis, or you will not know how to color the points.
plot(iris[, -5], col=iris$Species)

Unfortunately there is no simple equivelant for pairwise plots in ggplot2, so we are forced to look at the variabels one at a time, or write our own wrapper function (homework??):
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width, color = Species)) +
geom_point(size = 2)

Exercises
1. Which destinations have the highest average delays?
2. How many flights to Los Angeles (LAX) did each of the legacy carriers (AA, UA, DL or US) have in May from JFK, and what was their average duration?
3. Which plane (check the tail number) flew out of each New York airport the most?
4. What was the shortest flight out of each airport in terms of distance? In terms of duration?
5. Which date should you fly on if you want to have the lowest possible average departure delay? What about arrival delay?
Plot with 
6. Create a time series plot of each of the legacy carriers average departure delay by day and origin airport. It should look something like this:

Now Do It Yourself
7. You have probably had enough of New York City flight times. Now take a look at either the `mtcars` or `mtcars2` data set and execute many of the same commands above while exploring this new, different data set.
Merging Data
No data wrangling tutorial would be complete without a section on merging data. This will happen often. Data will come to you, perhaps from various sources, and you must combine the records (cases, samples, rows, etc.) based on a common indicator variable, usually some sort of key identifier variable (e.g. a name, a patient ID, etc.).
Joining Data
addr <- data.frame(name = c("Alice", "Bob",
"Carol", "dave", "Eve"),
email = c("alice@company.com",
"bob@company.com",
"carol@company.com",
"dave@company.com",
"eve@company.com"),
stringsAsFactors = FALSE)
addr
phone <- data.frame(name = c("Bob", "Carol",
"Eve", "Eve", "Frank"),
phone = c("919 555-1111",
"919 555-2222",
"919 555-3333",
"310 555-3333",
"919 555-4444"),
stringsAsFactors = FALSE)
phone
Outer Join
In dplyr:
full_join(addr, phone)
Joining, by = "name"
In base R:
merge(addr, phone, all = TRUE)
Inner Join
In dplyr:
inner_join(addr, phone)
Joining, by = "name"
In base R:
merge(addr, phone, all = FALSE)
Left Join
In dplyr:
left_join(addr, phone)
Joining, by = "name"
In base R:
merge(addr, phone, all.x = TRUE)
Right Join
In dplyr:
right_join(addr, phone)
Joining, by = "name"
In base R:
merge(addr, phone, all.y = TRUE)
Semi and Anti Joins
semi_join(addr, phone) # semi
Joining, by = "name"
anti_join(addr, phone) # anti
Joining, by = "name"
Special case: many-to-many relationships
addr2 <- data.frame(name = c("Alice", "Alice", "Bob", "Bob"),
email= c("alice@company.com",
"alice@gmail.com",
"bob@company.com",
"bob@hotmail.com"),
stringsAsFactors = FALSE)
phone2 <- data.frame(name = c("Alice", "Alice", "Bob", "Bob"),
phone = c("919 555-1111", "310 555-2222",
"919 555-3333", "310 555-3333"),
stringsAsFactors = FALSE)
In dplyr:
full_join(addr2, phone2, by = "name")
In base R:
merge(addr2, phone2)
Example: Enhancing NYC Flight Data
The nycflights13 package also contains additional information about:
weather |
hourly meterological data for each airport |
planes |
construction information about each plane |
airports |
airport names and locations |
airlines |
translation between two letter carrier codes and names |
Weather and Flight Delays
Let’s take a quick look at the weather data, with an eye towards examining how it might affect things like flight departure delays.
weather
Join by?
Variable defintions in the weather dataset:
origin |
Weather station. Named origin to faciliate merging with flights data |
year, month, day, hour |
Time of recording |
temp, dewp |
Temperature and dewpoint in Fahrenheit |
humid |
Relative humidity |
wind_dir, wind_speed, wind_gust |
Wind direction (in degrees), speed (in mph), and gust speed (in mph) |
precip |
Preciptation, in inches |
pressure |
Sea level pressure in millibars |
visib |
Visibility in miles |
intersect(names(weather), names(flights))
[1] "origin" "year" "month" "day" "hour" "time_hour"
Joining flights and weather
When joining these two data frames I will use an inner join (because I only want the flights that have the weather data, and I only want weather data when there was a flight)
Let’s make a ggplot of the delay time vs. visibility:
flightsw %>%
ggplot(aes(x = visib, y = dep_delay)) +
geom_point()

Let’s take a closer look at the dependent variable: visibility:
flightsw %>%
ggplot(aes(x = visib)) +
geom_histogram(fill = "blue")

table(flightsw$visib)
0 0.06 0.12 0.25 0.5 0.75 1 1.25 1.5 1.75 2 2.5 3
194 123 429 1361 1207 235 926 129 1096 201 2387 1187 2572
4 5 6 7 8 9 10
1526 2703 2888 3289 2929 3973 109674
Wow! The vast majority of flights are under clear skies with > 10 miles visibility. Good to know.

There are many negative departure time values (early!), which stop us from plotting the y-axis on a log-scale (which might be more visually appealing) … let’s boxplot but remove them first:
flightsw %>%
filter(dep_delay > 0) %>%
mutate(visib %<>% factor) %>%
ggplot(aes(x = visib, y = dep_delay, group = visib)) +
geom_boxplot(fill = "blue", color = "gray") +
coord_trans(y = "log10") +
xlab("Visibility") +
ylab("Departure Delay")

More Exercises
Check some of the other weather variables (e.g. temp, wind_speed, etc) and see if you can find any relationship between them and departure delay (dep_delay).
Merge the flights data with the planes data set (pay attention to what columns are being used for the join). Are older planes more likely to be delayed? What about planes from Airbus vs Boeing vs Embraer?
Acknowledgments
Above materials are derived in part from the following sources:
LS0tCnRpdGxlOiAiRGF0YSBXcmFuZ2xpbmcgaW4gUiIKYXV0aG9yOiAiU2ltb24gVGF2ZW5lciAmIFN0dSBGaWVsZCIKZGF0ZTogImByIGZvcm1hdChTeXMuRGF0ZSgpLCAnJWUgJUIgJVknKWAiCm91dHB1dDoKICBodG1sX25vdGVib29rOiBkZWZhdWx0CnJhdGlvOiAnOToxNicKdGFibGVzOiB5ZXMKZm9udHNpemU6IDEycHQKLS0tCgoKCiMgU2V0dXAgJiBDb25maWd1cmF0aW9uCgoqIFRoZSBjb2RlIGJlbG93IHdpbGwgbG9hZCB0aGUgbGlicmFyaWVzIHlvdSB3aWxsIG5lZWQgZm9yIHRoaXMgdHV0b3JpYWwuCmBgYHtyIHNldHVwfQpvcHRpb25zKHdpZHRoID0gMTAwKQpsaWJyYXJ5KHRpYmJsZSkgICAgICAgICAgICAgICAgICAgICAgICAgIyBzcGVjaWFsIHR5cGUgb2YgZGF0YSBmcmFtZQpsaWJyYXJ5KG1hZ3JpdHRyKSAgICAgICAgICAgICAgICAgICAgICAgIyBwaXBlcwpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkoZHBseXIpKSAgICAgICAgIyBkYXRhIG1hbmlwdWxhdGlvbgpsaWJyYXJ5KG55Y2ZsaWdodHMxMykgICAgICAgICAgICAgICAgICAgIyB0b3kgZGF0YSBzZXQKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KGx1YnJpZGF0ZSkpICAgICMgd29ya2luZyB3aXRoIGRhdGVzL3RpbWVzCmxpYnJhcnkoZ2dwbG90MikgICAgICAgICAgICAgICAgICAgICAgICAjIHByZXR0eSBwbG90cwpsaWJyYXJ5KHJlc2hhcGUyKSAgICAgICAgICAgICAgICAgICAgICAgIyBtZWx0L3Jlc2hhcGUgZGF0YSBmcmFtZXMKYGBgCiogVGhlIGRhdGEgd2Ugd2lsbCBiZSB1c2luZyB0b2RheSBpcyBhdmFpbGFibGUgdmlhIHRoZSBgbnljZmxpZ2h0czEzYCBwYWNrYWdlLgoqIFIgZW52aXJvbm1lbnQ6CiAgICArICBOb3cgaXMgYSBnb29kIHRpbWUgdG8gY2hlY2sgdGhhdCB5b3VyIHZlcnNpb24gb2YgUiBpcyB1cC10by1kYXRlLiBJZiB5b3UgYXJlIG5vdCBydW5uaW5nIHZlcnNpb24gMy4yLjMgb3IgbGF0ZXIgaXQgaXMgdGltZSB0byB1cGRhdGUuIFdlIHdpbGwgdXNlIGEgZnVuY3Rpb24gZnJvbSB0aGUgYGRldnRvb2xzYCBwYWNrYWdlIHRvIGNoZWNrIHlvdXIgc2Vzc2lvbiBpbmZvOgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpkZXZ0b29sczo6c2Vzc2lvbl9pbmZvKCkKYGBgCgoKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKCgojIFBpcGVzIChgbWFncml0dHJgKSAgPGltZyBzcmM9ImltYWdlcy9tYWdyaXR0ci5wbmciIGNsYXNzPSJ0aXRsZS1oZXgiPiAgIVtQaXBlXShpbWFnZXMvcGlwZS5qcGVnKQoKVGhpcyBpcyBhbiBpZGVhIHRoYXQgaGFzIGJlZW4gYXJvdW5kIGZvciBhIGxvbmcgdGltZSwgcGFydGljdWxhcmx5IGluIHRoZSBMaW51eC9Vbml4IGVjb3N5c3RlbSwgd2hlcmUgaXQgaXMgZGVub3RlZCB3aXRoIHRoZSAifCIgc3ltYm9sOgoKKiBDb21wbGV4IHRhc2tzIGNhbiBiZSBhY2NvbXBsaXNoZWQgYnkgc3RyaW5naW5nIHRvZ2V0aGVyIG1hbnkgc2ltcGxlIHRvb2xzIC0gc3ludGF4IGlzIGltcG9ydGFudCBmb3IgZWZmaWNpZW5jeSAvIHJlYWRhYmlsaXR5CiogVGhlIGlkZWEgb2YgcGlwZXMgaXMgdGhhdCB0aGUgcmVzdWx0IG9mIHRoZSBwcmV2aW91cyBjb21tYW5kIGlzIHBhc3NlZCB0byB0aGUgbmV4dCBjb21tYW5kLCB3aGljaCBhbGxvd3MgbWFueSB0b29scyB0byBiZSBzdHJ1bmcgdG9nZXRoZXIgLSBmcm9tIGxlZnQgdG8gcmlnaHQuCiogSW4gUiwgYG1hZ3JpdHRyYCBpbnRyb2R1Y2VzIHRoZSBgJT4lYCBwaXBlIG9wZXJhdG9yIHdoaWNoIHJ1bnMgdGhlIGV4cHJlc3Npb24gb2YgdGhlIGxlZnQgYW5kIHBhc3NlcyB0aGUgcmVzdWx0IGFzIHRoZSAqZmlyc3QqIGFyZ3VtZW50IHRvIGV4cHJlc3Npb24gb24gdGhlIHJpZ2h0LgoKWW91IGNhbiB0aGluayBhYm91dCB0aGUgZm9sbG93aW5nIHNlcXVlbmNlIG9mIGFjdGlvbnM6CgogICAgMS4gZmluZCBrZXkKICAgIDIuIHVubG9jayBjYXIKICAgIDMuIHN0YXJ0IGNhcgogICAgNC4gZHJpdmUgdG8gc2Nob29sCiAgICA1LiBwYXJrLgogIApFeHByZXNzZWQgYXMgYSBzZXQgb2YgbmVzdGVkIGZ1bmN0aW9ucyBpbiBSIGBwc2V1ZG9jb2RlYCB0aGlzIHdvdWxkIGxvb2sgbGlrZToKCmBgYHtyLCBldmFsID0gRkFMU0V9CnBhcmsoZHJpdmUoc3RhcnRfY2FyKGZpbmQoImtleXMiKSksIHRvPSJjYW1wdXMiKSkKYGBgCgpXcml0aW5nIGl0IG91dCB1c2luZyBwaXBlcyBnaXZlIGl0IGEgbW9yZSBuYXR1cmFsIChhbmQgZWFzaWVyIHRvIHJlYWQhKSBzdHJ1Y3R1cmU6CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpmaW5kKCJrZXlzIikgJT4lCiAgICBzdGFydF9jYXIoKSAlPiUKICAgIGRyaXZlKHRvPSJjYW1wdXMiKSAlPiUKICAgIHBhcmsoKQpgYGAKCiMgQXBwcm9hY2hlcwoKQWxsIG9mIHRoZSBmb2xsb3dpbmcgYXJlIGZpbmUsIGl0IGNvbWVzIGRvd24gdG8gcGVyc29uYWwgcHJlZmVyZW5jZSAoYW5kIElNSE8sIHJlYWRhYmlsaXR5KToKCmBgYHtyLCBldmFsID0gRkFMU0V9CiMgTmVzdGVkOgpoKCBnKCBmKHgpLCB5ID0gMSksIHogPSAxICkKCiMgUGlwZWQ6CmYoeCkgJT4lIGcoeSA9IDEpICU+JSBoKHogPSAxKQoKIyBJbnRlcm1lZGlhdGU6CnJlcyA8LSBmKHgpCnJlcyA8LSBnKHJlcywgeSA9IDEpCnJlcyA8LSBoKHJlcywgeiA9IDEpCmBgYAoKCldoYXQgYWJvdXQgb3RoZXIgYXJndW1lbnRzPwoKU29tZXRpbWVzIHdlIHdhbnQgdG8gc2VuZCBvdXIgcmVzdWx0cyB0byBhbiBmdW5jdGlvbiBhcmd1bWVudCBvdGhlciB0aGFuIGZpcnN0IG9uZSBvciB3ZSB3YW50IHRvIHVzZSB0aGUgcHJldmlvdXMgcmVzdWx0IGZvciBtdWx0aXBsZSBhcmd1bWVudHMuIEluIHRoZXNlIGNhc2VzIHdlIGNhbiByZWZlciB0byB0aGUgcHJldmlvdXMgcmVzdWx0IHVzaW5nICIuIi4KCmBgYHtyfQpkYXRhLmZyYW1lKGEgPSAxOjMsIGIgPSAzOjEpICU+JSBsbShhIH4gYiwgZGF0YSA9IC4pCmRhdGEuZnJhbWUoYSA9IDE6MywgYiA9IDM6MSkgJT4lIC5bWzFdXQpkYXRhLmZyYW1lKGEgPSAxOjMsIGIgPSAzOjEpICU+JSAuW1tsZW5ndGgoLildXQpgYGAKCgojIFRpYmJsZXMKIyMgTW9kZXJuIGRhdGEgZnJhbWVzCgpIYWRsZXkgV2lja2hhbSBoYXMgYSBwYWNrYWdlIHRoYXQgbW9kaWZpZXMgZGF0YSBmcmFtZXMgdG8gYmUgbW9yZSBtb2Rlcm4sIG9yIGFzIGhlIGNhbGxzIHRoZW0gKnN1cmx5KiBhbmQgKmxhenkqLiBUaGF0IGlzIHRoZXkgZG9uJ3QgY2hhbmdlIHZhcmlhYmxlIG5hbWVzIG9yIHR5cGVzLCBhbmQgZG9uJ3QgZG8gcGFydGlhbCBtYXRjaGluZzsgYW5kIHRoZXkgY29tcGxhaW4gbW9yZSAoZS5nLiB3aGVuIGEgdmFyaWFibGUgZG9lcyBub3QgZXhpc3QpLiBUaGlzIGZvcmNlcyB5b3UgdG8gY29uZnJvbnQgcHJvYmxlbXMgZWFybGllciwgdHlwaWNhbGx5IGxlYWRpbmcgdG8gY2xlYW5lciwgbW9yZSBleHByZXNzaXZlIGNvZGUuIFRpYmJsZXMgYWxzbyBoYXZlIGFuIGVuaGFuY2VkIGBwcmludCgpYCBtZXRob2Qgd2hpY2ggbWFrZXMgdGhlbSBlYXNpZXIgdG8gdXNlIHdpdGggbGFyZ2UgZGF0YXNldHMgY29udGFpbmluZyBjb21wbGV4IG9iamVjdHMuCgpgYGB7cn0KY2xhc3MoaXJpcykKdGJsX2lyaXMgPC0gYXMudGliYmxlKGlyaXMpCmNsYXNzKHRibF9pcmlzKQpgYGAKCiMjIFByaW50aW5nCmBgYHtyfQp0YmxfaXJpcwpgYGAKCiMjIExhenkgdGliYmxlcwpgYGB7cn0KdGJsX2lyaXNbMSwgXQp0YmxfaXJpc1ssICJTcGVjaWVzIl0KYGBgCgoKIyMgTGF6eSB0aWJibGVzIChmYWN0b3JzKQpgYGB7cn0KZGF0YV9mcmFtZSh4ID0gMTozLCB5ID0gYygiQSIsICJCIiwgIkMiKSkKYGBgCgoKIyMgU3VybHkgVGliYmxlcwpgYGB7ciBCYWRUaWJibGUsIGVycm9yID0gVFJVRX0KdGJsX2lyaXNbLCAiTmFtZSJdICAgICAgIyBlcnJvcnMgb3V0OyBpbnZhbGlkIGluZGV4CnRibF9pcmlzW1siTmFtZSJdXSAgICAgICMgc2FtZSBhcyBhYm92ZTsgZGlmZmVyZW50IHN5bnRheApgYGAKCmBgYHtyIEJhZFRpYmJsZTJ9CmlyaXMkTmFtZSAgICAgICAgICAgICAgICMgcmV0dXJucyBOVUxMCnRibF9pcmlzJE5hbWUgICAgICAgICAgICMgcmV0dXJucyBOVUxMICh3aXRoIGV4cGxhbmF0aW9uISkKYGBgCgpgYGB7ciBCYWRUaWJibGUzfQp0YmxfaXJpc1sxNjAsIF0gICAgICAgICAjIGV4Y2VlZGVkIGluZGV4OyByZXR1cm5zIDEgcm93IGRhdGEgZnJhbWUgb2YgTkFzCmBgYAoKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCgojIGBkcGx5cmAgPGltZyBzcmM9ImltYWdlcy9kcGx5ci5wbmciIGNsYXNzPSJ0aXRsZS1oZXgiPgojIyBBIEdyYW1tYXIgb2YgRGF0YSBNYW5pcHVsYXRpb24KClRoZSBgZHBseXJgIHBhY2thZ2UgaXMgYmFzZWQgb24gdGhlIGNvbmNlcHRzIG9mIGZ1bmN0aW9ucyBhcyB2ZXJicyB0aGF0IG1hbmlwdWxhdGUgZGF0YSBmcmFtZXMuCgpTaW5nbGUgZGF0YSBmcmFtZSBmdW5jdGlvbnMgLyB2ZXJiczoKCiogYHRibF9kZigpYDogYWRkIHRoZSB0YmxfZGYgY2xhc3MKKiBgZmlsdGVyKClgOiBwaWNrIHJvd3MgbWF0Y2hpbmcgY3JpdGVyaWEKKiBgc2xpY2UoKWA6IHBpY2sgcm93cyB1c2luZyBpbmRleChlcykKKiBgc2VsZWN0KClgOiBwaWNrIGNvbHVtbnMgYnkgbmFtZQoqIGBwdWxsKClgOiBncmFiIGEgY29sdW1uIGFzIGEgdmVjdG9yCiogYHJlbmFtZSgpYDogcmVuYW1lIHNwZWNpZmljIGNvbHVtbnMKKiBgYXJyYW5nZSgpYDogcmVvcmRlciByb3dzCiogYG11dGF0ZSgpYDogYWRkIG5ldyB2YXJpYWJsZXMKKiBgdHJhbnNtdXRlKClgOiBjcmVhdGUgbmV3IGRhdGEgZnJhbWUgd2l0aCB2YXJpYWJsZXMKKiBgZGlzdGluY3QoKWA6IGZpbHRlciBmb3IgdW5pcXVlIHJvd3MKKiBgc2FtcGxlX24oKSAvIHNhbXBsZV9mcmFjKClgOiByYW5kb21seSBzYW1wbGUgcm93cwoqIGBzdW1tYXJpc2UoKWA6IHJlZHVjZSB2YXJpYWJsZXMgdG8gdmFsdWVzCiogLi4uIGFuZCBtYW55IG1vcmUKCgojIyBkcGx5ciBydWxlcyBmb3IgZnVuY3Rpb25zCgogICAgMS4gRmlyc3QgYXJndW1lbnQgaXMgYWx3YXlzIGEgZGF0YSBmcmFtZQogICAgMi4gU3Vic2VxdWVudCBhcmd1bWVudHMgc2F5IHdoYXQgdG8gZG8gd2l0aCB0aGF0IGRhdGEgZnJhbWUKICAgIDMuIEFsd2F5cyByZXR1cm4gYSBkYXRhIGZyYW1lCiAgICA0LiBEbyBub3QgbW9kaWZ5IGluIHBsYWNlCiAgICA1LiBQZXJmb3JtYW5jZSB2aWEgbGF6eSBldmFsdWF0aW9uCgoKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgoKIyBFeGFtcGxlIERhdGEKV2Ugd2lsbCBkZW1vbnN0cmF0ZSBkcGx5ciBmdW5jdGlvbmFsaXR5IHVzaW5nIHRoZSBgbnljZmxpZ2h0czEzYCBkYXRhIHdoaWNoIGNvbnRhaW5zIGRhdGEgb24gZmxpZ2h0cyBkZXBhcnRpbmcgZnJvbSBOZXcgWW9yayBDaXR5LiBJdCB3YXMgd3JpdHRlbiBieSBIYWRsZXkgV2lja2hhbSBhbmQgaW5jbHVkZXMgYWlybGluZSBvbi10aW1lIGRhdGEgZm9yIGFsbCBmbGlnaHRzIGRlcGFydGluZyBOWUMgaW4gMjAxMyBhcyB3ZWxsIGFzIHVzZWZ1bCAqbWV0YWRhdGEqIG9uIGFpcmxpbmVzLCBhaXJwb3J0cywgd2VhdGhlciwgYW5kIHBsYW5lcyAoc2VlIGA/bnljZmxpZ2h0czEzYCkuCgojIyBTdHJ1Y3R1cmUgb2YgYGZsaWdodHNgCgpgYGB7ciBmbGlnaHRzX3N0dWN0dXJlfQpmbGlnaHRzICU8PiUgYXMudGliYmxlICAgICMgJ3JlYm91bmQnIHBpcGU7IGFrYSBjb21wb3VuZCBhc3NpZ25tZW50CnN0cihmbGlnaHRzKQpgYGAKCgojIyBMb29rIGF0IGZ1bGwgInRpYmJsZSIiIGRhdGEgZnJhbWUKYGBge3IgZmxpZ2h0c190aWJibGV9CmZsaWdodHMKYGBgCgoKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKCiMgYGRwbHlyYCBWZXJicwojIyBNYXJjaCBmbGlnaHRzIGBkcGx5cjo6ZmlsdGVyYApgYGB7cn0KZmxpZ2h0cyAlPiUgZmlsdGVyKG1vbnRoID09IDMpCmBgYAoKIyMgRmlyc3Qgd2VlayBvZiBNYXJjaCBmbGlnaHRzIGBkcGx5cjo6ZmlsdGVyYApgYGB7cn0KZmxpZ2h0cyAlPiUgZmlsdGVyKG1vbnRoID09IDMsIGRheSA8PSA3KQpgYGAKCiMjIEZsaWdodHMgdG8gTEFYIG9yIFJEVSBpbiBNYXJjaCBgZHBseXI6OmZpbHRlcmAKYGBge3J9CmZsaWdodHMgJT4lIGZpbHRlcihkZXN0ID09ICJMQVgiIHwgZGVzdCA9PSAiUkRVIiwgbW9udGg9PTMpCmBgYAoKIyMgRmlyc3QgMTAgZmxpZ2h0cyBgZHBseXI6OnNsaWNlYApgYGB7cn0KZmxpZ2h0cyAlPiUgc2xpY2UoMToxMCkKYGBgCgojIyBMYXN0IDUgZmxpZ2h0cyBgZHBseXI6OnNsaWNlYApgYGB7cn0KZmxpZ2h0cyAlPiUgc2xpY2UoKG4oKSAtIDUpOm4oKSkKYGBgCgojIyBTZWxlY3QgY29sdW1ucyBgZHBseXI6OnNlbGVjdGAKYGBge3J9CmZsaWdodHMgJT4lIHNlbGVjdCh5ZWFyLCBtb250aCwgZGF5KQpgYGAKCiMjIEV4Y2x1ZGUgY29sdW1ucyBgZHBseXI6OnNlbGVjdGAKYGBge3J9CmZsaWdodHMgJT4lIHNlbGVjdCgteWVhciwgLW1vbnRoLCAtZGF5KQpgYGAKCiMjIFNlbGVjdCByYW5nZXMgYGRwbHlyOjpzZWxlY3RgCmBgYHtyfQpmbGlnaHRzICU+JSBzZWxlY3QoeWVhcjpkYXkpCmBgYAoKIyMgRXhjbHVkZSByYW5nZXMgYGRwbHlyOjpzZWxlY3RgCmBgYHtyfQpmbGlnaHRzICU+JSBzZWxlY3QoLSh5ZWFyOmRheSkpCmBgYAoKIyMgU2VsZWN0IG1hdGNoaW5nIGBkcGx5cjo6c2VsZWN0YApgYGB7cn0KZmxpZ2h0cyAlPiUgc2VsZWN0KGNvbnRhaW5zKCJkZXAiKSwgCiAgICAgICAgICAgICAgICAgICBjb250YWlucygiYXJyIikpCgpmbGlnaHRzICU+JSBzZWxlY3Qoc3RhcnRzX3dpdGgoImRlcCIpLCAKICAgICAgICAgICAgICAgICAgIHN0YXJ0c193aXRoKCJhcnIiKSkKYGBgCgojIyBPdGhlciBoZWxwZXJzOgogICAtIGBjdXJyZW50X3ZhcnNgCiAgIC0gYHN0YXJ0c193aXRoYAogICAtIGBlbmRzX3dpdGhgCiAgIC0gYGNvbnRhaW5zYAogICAtIGBtYXRjaGVzYAogICAtIGBudW1fcmFuZ2VgCiAgIC0gYG9uZV9vZmAKICAgLSBgZXZlcnl0aGluZ2AKCgojIyBVc2luZyBgZHBseXI6OnB1bGxgCmBgYHtyIHB1bGx9Cm5hbWVzKGZsaWdodHMpCmZsaWdodHMgJT4lIHB1bGwoInllYXIiKSAlPiUgaGVhZCgpCmZsaWdodHMgJT4lIHB1bGwoMSkgJT4lIGhlYWQoKQpmbGlnaHRzICU+JSBwdWxsKC0xKSAlPiUgaGVhZCgpCmBgYAoKIyMgQ2hhbmdlIGNvbHVtbiBuYW1lcyBgZHBseXI6OnJlbmFtZWAKYGBge3J9CmZsaWdodHMgJT4lIHJlbmFtZSh0YWlsX251bWJlciA9IHRhaWxudW0pCmBgYAoKIyMgc2VsZWN0KCkgdnMuIHJlbmFtZSgpCmBgYHtyfQpmbGlnaHRzICU+JSBzZWxlY3QodGFpbF9udW1iZXIgPSB0YWlsbnVtLCAtdGFpbG51bSkKZmxpZ2h0cyAlPiUgc2VsZWN0KC10YWlsbnVtLCB0YWlsX251bWJlciA9IHRhaWxudW0pCmBgYAoKIyMgU29ydCBkYXRhIGBkcGx5cjo6YXJyYW5nZWAKYGBge3J9CmZsaWdodHMgJT4lIGZpbHRlcihtb250aCA9PSAzLCBkYXkgPT0gMikgJT4lIGFycmFuZ2Uob3JpZ2luLCBkZXN0KQpgYGAKCgojIyBEZXNjZW5kaW5nIG9yZGVyIGBkcGx5cjo6YXJyYW5nZWAKYGBge3J9CmZsaWdodHMgJT4lIGZpbHRlcihtb250aCA9PSAzLCBkYXkgPT0gMikgJT4lCiAgYXJyYW5nZShkZXNjKG9yaWdpbiksIGRlc3QpICU+JSBzZWxlY3Qob3JpZ2luLCBkZXN0LCB0YWlsbnVtKQoKYGBgCgojIyBNb2RpZnkgY29sdW1ucyBgZHBseXI6Om11dGF0ZWAKYGBge3J9CmZsaWdodHMgJT4lCiAgIHNlbGVjdCgxOjMpICU+JQogICBtdXRhdGUoZGF0ZSA9IHBhc3RlKG1vbnRoLCBkYXksIHllYXIsIHNlcD0iLyIpICU+JSBtZHkoKSkKYGBgCgojIyBDcmVhdGUgbmV3IHRpYmJsZSBmcm9tIGV4aXN0aW5nIGNvbHVtbnMgYGRwbHlyOjp0cmFuc211dGVgCmBgYHtyfQpmbGlnaHRzICU+JQogICBzZWxlY3QoMTozKSAlPiUKICAgdHJhbnNtdXRlKGRhdGUgPSBwYXN0ZShtb250aCwgZGF5LCB5ZWFyLCBzZXA9Ii8iKSAlPiUgbWR5KCkpCmBgYAoKIyMgRmluZCB1bmlxdWUgcm93cyBgZHBseXI6OmRpc3RpbmN0YApgYGB7cn0KZmxpZ2h0cyAlPiUKICAgc2VsZWN0KG9yaWdpbiwgZGVzdCkgJT4lCiAgIGRpc3RpbmN0KCkgJT4lCiAgIGFycmFuZ2Uob3JpZ2luLCBkZXN0KQpgYGAKCiMjIFNhbXBsZSByb3dzIGBkcGx5cjo6c2FtcGxlX25gCmBgYHtyfQpmbGlnaHRzICU+JSBzYW1wbGVfbigxMCkKYGBgCgojIyBTYW1wbGUgcm93cyBgZHBseXI6OnNhbXBsZV9mcmFjYApgYGB7cn0KZmxpZ2h0cyAlPiUgc2FtcGxlX2ZyYWMoMC4wMDEpCmBgYAoKIyMgU3VtbWFyeSB2aWEgYGRwbHlyOjpzdW1tYXJpc2VgCmBgYHtyfQpmbGlnaHRzICU+JSBtdXRhdGUoZGF0ZSA9IHBhc3RlKG1vbnRoLCBkYXksIHllYXIsIHNlcCA9ICIvIikgJT4lIG1keSgpKSAlPiUgCiAgIHN1bW1hcml6ZShuKCksIG1pbihkYXRlKSwgbWF4KGRhdGUpKQpgYGAKCiMjIFNldCBHcm91cGluZ3MgYGRwbHlyOjpncm91cF9ieWAKYGBge3J9CmZsaWdodHMgJT4lIGdyb3VwX2J5KG9yaWdpbikKYGBgCgojIyBTdW1tYXJpemUgR3JvdXBzIGBkcGx5cjo6c3VtbWFyaXNlIGRwbHlyOjpncm91cF9ieWAKYGBge3J9CmZsaWdodHMgJT4lCiAgZ3JvdXBfYnkob3JpZ2luKSAlPiUKICBtdXRhdGUoZGF0ZSA9IHBhc3RlKG1vbnRoLCBkYXksIHllYXIsIHNlcD0iLyIpICU+JSBtZHkoKSkgJT4lCiAgc3VtbWFyaXplKG4oKSwgbWluKGRhdGUpLCBtYXgoZGF0ZSkpCmBgYAoKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgoKIyBQbG90dGluZwoKTm8gZGF0YSB3cmFuZ2xpbmcgYW5kL29yIGRhdGEgZXhwbG9yYXRpb24gaXMgY29tcGxldGUgd2l0aG91dCBhdCBsZWFzdCBzb21lIHZpc3VhbCBleHBsb3JhdGlvbiBvZiB0aGUgZGF0YS4gQXNraW5nIHlvdXJzZWxmOiB3aGF0IGRvIEkgaGF2ZSBoZXJlPyBob3cgaXMgbXkgZGF0YSAocGFydGljdWxhcmx5IG15IGNsYXNzIHZhcmlhYmxlIG9yIHJlc3BvbnNlIHZhcmlhYmxlKSBkaXN0cmlidXRlZD8gQXJlIHRoZXJlIGFueSBtaXNzaW5nIHZhbHVlcz8gTmVnYXRpdmUgdmFsdWVzPyAKCmBgYHtyfQpwYXIobWZyb3cgPSBjKDIsIDIpKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBtYWtlIDJ4MiBncmlkIGZvciBwbG90cwpwYXIobWdwID0gYygyLCAwLjc1LCAwKSwgbWFyID0gYygzLCA0LCAzLCAxKSkgICAgIyBncmFwaGljcyBzZXR0aW5nczsgc3F1ZWV6ZSBtYXJnaW5zCmxhcHBseShuYW1lcyhpcmlzKVstNV0sIGZ1bmN0aW9uKGNsYXNzKQogICBib3hwbG90KHNwbGl0KGlyaXNbW2NsYXNzXV0sIGlyaXMkU3BlY2llcyksIG1haW4gPSBjbGFzcywgY29sID0gMTozKSkgJT4lCiAgIGludmlzaWJsZSAgICMgcGlwZSB0byBpbnZpc2libGUgdG8gc3VwcHJlc3Mgb3V0cHV0CmBgYAoKIyMgVGhlIHNhbWUgdmlhIDxpbWcgc3JjPSJpbWFnZXMvZ2dwbG90Mi5wbmciIGNsYXNzPSJ0aXRsZS1oZXgiPgpgYGB7cn0KdGhtIDwtIHRoZW1lX2J3KCkgKwogIHRoZW1lKAogICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3VyID0gTkEpLAogICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAidHJhbnNwYXJlbnQiLCBjb2xvdXIgPSBOQSksCiAgICBsZWdlbmQucG9zaXRpb24gPSAidG9wIiwKICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAidHJhbnNwYXJlbnQiLCBjb2xvdXIgPSBOQSksCiAgICBsZWdlbmQua2V5ID0gZWxlbWVudF9yZWN0KGZpbGwgPSAidHJhbnNwYXJlbnQiLCBjb2xvdXIgPSBOQSkKICApCnRoZW1lX3NldCh0aG0pCgpkZiA8LSByZXNoYXBlMjo6bWVsdChpcmlzLCBpZC52YXJzID0gIlNwZWNpZXMiLCB2YXJpYWJsZS5uYW1lID0gIkZlYXR1cmUiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZS5uYW1lID0gImNtIikgJT4lCiAgbXV0YXRlKEZlYXR1cmU9Z3N1YigiXFwuIiwgIiAiLCBGZWF0dXJlKSkKCmdncGxvdChkZiwgYWVzKHkgPSBjbSwgeCA9IFNwZWNpZXMsIGZpbGwgPSBTcGVjaWVzKSkgKwogIGdlb21fYm94cGxvdChjb2xvciA9ICIjMUYzNTUyIiwgYWxwaGEgPSAwLjc1LCBzaXplID0gMC41KSArCiAgc2NhbGVfeF9kaXNjcmV0ZShuYW1lID0gIlNwZWNpZXMiKSArCiAgZ2d0aXRsZSgiT3ZlcmFsbCBUaXRsZSIpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKwogIGZhY2V0X3dyYXAofkZlYXR1cmUsIG5jb2wgPSAyKSAKYGBgCgoKIyMgUGFpcndpc2UgcGxvdHMKClZlcnkgdXNlZnVsIGZvciB2aXN1YWxseSBpbnNwZWN0aW5nIHZhcmlhYmxlcyBmb3IgcGF0dGVybnMgb2YgaW50ZXJlc3QuIFlvdSB3aWxsIHR5cGljYWxseSBuZWVkIHRvIGtub3cgeW91ciBvdXRjb21lIG9mIGludGVyZXN0ICphIHByaW9yaSosIGkuZS4gc3VwZXJ2aXNlZCBhbmFseXNpcywgb3IgeW91IHdpbGwgbm90IGtub3cgaG93IHRvIGNvbG9yIHRoZSBwb2ludHMuCgpgYGB7cn0KcGxvdChpcmlzWywgLTVdLCBjb2w9aXJpcyRTcGVjaWVzKQpgYGAKClVuZm9ydHVuYXRlbHkgdGhlcmUgaXMgbm8gc2ltcGxlIGVxdWl2ZWxhbnQgZm9yIHBhaXJ3aXNlIHBsb3RzIGluIGBnZ3Bsb3QyYCwgc28gd2UgYXJlIGZvcmNlZCB0byBsb29rIGF0IHRoZSB2YXJpYWJlbHMgb25lIGF0IGEgdGltZSwgb3Igd3JpdGUgb3VyIG93biB3cmFwcGVyIGZ1bmN0aW9uIChob21ld29yaz8/KToKCmBgYHtyfQpnZ3Bsb3QoaXJpcywgYWVzKHggPSBTZXBhbC5MZW5ndGgsIHkgPSBTZXBhbC5XaWR0aCwgY29sb3IgPSBTcGVjaWVzKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDIpCmBgYAoKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKCiMgRXhlcmNpc2VzCgogICAgMS4gV2hpY2ggZGVzdGluYXRpb25zIGhhdmUgdGhlIGhpZ2hlc3QgYXZlcmFnZSBkZWxheXM/CgogICAgMi4gSG93IG1hbnkgZmxpZ2h0cyB0byBMb3MgQW5nZWxlcyAoTEFYKSBkaWQgZWFjaCBvZiB0aGUgbGVnYWN5IGNhcnJpZXJzIChBQSwgVUEsIERMIG9yIFVTKSBoYXZlIGluIE1heSBmcm9tIEpGSywgYW5kIHdoYXQgd2FzIHRoZWlyIGF2ZXJhZ2UgZHVyYXRpb24/CgogICAgMy4gV2hpY2ggcGxhbmUgKGNoZWNrIHRoZSB0YWlsIG51bWJlcikgZmxldyBvdXQgb2YgZWFjaCBOZXcgWW9yayBhaXJwb3J0IHRoZSBtb3N0PwoKICAgIDQuIFdoYXQgd2FzIHRoZSBzaG9ydGVzdCBmbGlnaHQgb3V0IG9mIGVhY2ggYWlycG9ydCBpbiB0ZXJtcyBvZiBkaXN0YW5jZT8gSW4gdGVybXMgb2YgZHVyYXRpb24/CgogICAgNS4gV2hpY2ggZGF0ZSBzaG91bGQgeW91IGZseSBvbiBpZiB5b3Ugd2FudCB0byBoYXZlIHRoZSBsb3dlc3QgcG9zc2libGUgYXZlcmFnZSBkZXBhcnR1cmUgZGVsYXk/IFdoYXQgYWJvdXQgYXJyaXZhbCBkZWxheT8KCgojIyMgUGxvdCB3aXRoIDxpbWcgc3JjPSJpbWFnZXMvZ2dwbG90Mi5wbmciIGNsYXNzPSJ0aXRsZS1oZXgiPgoKICAgIDYuIENyZWF0ZSBhIHRpbWUgc2VyaWVzIHBsb3Qgb2YgZWFjaCBvZiB0aGUgbGVnYWN5IGNhcnJpZXJzIGF2ZXJhZ2UgZGVwYXJ0dXJlIGRlbGF5IGJ5IGRheSBhbmQgb3JpZ2luIGFpcnBvcnQuIEl0IHNob3VsZCBsb29rIHNvbWV0aGluZyBsaWtlIHRoaXM6CgpgYGB7ciBmbGlnaHRzX2dncGxvdCwgZWNobyA9IEZBTFNFfQoKZmxpZ2h0cyAlPiUKICBmaWx0ZXIoY2FycmllciA9PSBjKCJBQSIsICJVQSIsICJETCIsICJVUyIpKSAlPiUKICBncm91cF9ieShjYXJyaWVyLCBkYXksIG9yaWdpbikgJT4lCiAgc3VtbWFyaXNlKG4gPSBuKCksIGRlbGF5ID0gbWVhbihkZXBfZGVsYXksIG5hLnJtID0gVFJVRSkpICU+JQogIGdncGxvdChhZXMoeCA9IGRheSwgeSA9IGRlbGF5KSkgKwogICAgZ2VvbV9saW5lKCkgKwogICAgZmFjZXRfd3JhcChjYXJyaWVyIH4gb3JpZ2luKQpgYGAKCiMjIyBOb3cgRG8gSXQgWW91cnNlbGYKCiAgICA3LiBZb3UgaGF2ZSBwcm9iYWJseSBoYWQgZW5vdWdoIG9mIE5ldyBZb3JrIENpdHkgZmxpZ2h0IHRpbWVzLiBOb3cgdGFrZSBhIGxvb2sgYXQgZWl0aGVyIHRoZSBgbXRjYXJzYCBvciBgbXRjYXJzMmAgZGF0YSBzZXQgYW5kIGV4ZWN1dGUgbWFueSBvZiB0aGUgc2FtZSBjb21tYW5kcyBhYm92ZSB3aGlsZSBleHBsb3JpbmcgdGhpcyBuZXcsIGRpZmZlcmVudCBkYXRhIHNldC4KCgoKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgoKCgojIE1lcmdpbmcgRGF0YSAKCk5vIGRhdGEgd3JhbmdsaW5nIHR1dG9yaWFsIHdvdWxkIGJlIGNvbXBsZXRlIHdpdGhvdXQgYSBzZWN0aW9uIG9uIG1lcmdpbmcgZGF0YS4gVGhpcyB3aWxsIGhhcHBlbiBvZnRlbi4gRGF0YSB3aWxsIGNvbWUgdG8geW91LCBwZXJoYXBzIGZyb20gdmFyaW91cyBzb3VyY2VzLCBhbmQgeW91IG11c3QgY29tYmluZSB0aGUgcmVjb3JkcyAoY2FzZXMsIHNhbXBsZXMsIHJvd3MsIGV0Yy4pIGJhc2VkIG9uIGEgY29tbW9uIGluZGljYXRvciB2YXJpYWJsZSwgdXN1YWxseSBzb21lIHNvcnQgb2YgKmtleSogaWRlbnRpZmllciB2YXJpYWJsZSAoZS5nLiBhIG5hbWUsIGEgcGF0aWVudCBJRCwgZXRjLikuCgojIyBEYXRhIG1hbmlwdWxhdGlvbiBpcyBub3QgY29tcGxldGUgd2l0aG91dCBtZXJnaW5nIGluZm9ybWF0aW9uCgpUd28gdGFibGUgZnVuY3Rpb25zIC8gdmVyYnMsIGFsbCBmdW5jdGlvbnMgaGF2ZSB0aGUgZm9ybSBgZihhLCBiKWA6Cgp8ICAgRnVuY3Rpb24gICAgfCAgIFdoYXQgaXQgZG9lcyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgLS0tLS0tLS0tLS0tLSB8IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIHwKfCBgbGVmdF9qb2luYCAgIHwgSm9pbiBtYXRjaGluZyByb3dzIGZyb20gYGJgIHRvIGBhYCwgcHJlc2VydmluZyBhbGwgcm93cyBvZiBgYWAgfAp8IGByaWdodF9qb2luYCAgfCBKb2luIG1hdGNoaW5nIHJvd3MgZnJvbSBgYWAgdG8gYGJgLCBwcmVzZXJ2aW5nIGFsbCByb3dzIG9mIGBiYCB8CnwgYGlubmVyX2pvaW5gICB8IEpvaW4gZGF0YSwgcHJlc2VydmluZyBvbmx5IHJvd3Mgd2l0aCBrZXlzIGluIGJvdGggYGFgIGFuZCBgYmAgIHwKfCBgZnVsbF9qb2luYCAgIHwgSm9pbiBkYXRhLCBwcmVzZXJ2aW5nIGFsbCByb3dzIGluIGJvdGggYGFgIGFuZCBgYmAgICAgICAgICAgICAgfAp8IGBzZW1pX2pvaW5gICAgfCBTdWJzZXQgcm93cyBpbiBgYWAgdGhhdCBoYXZlIGEgbWF0Y2ggaW4gYGJgICAgICAgICAgICAgICAgICAgICB8CnwgYGFudGlfam9pbmAgICB8IFN1YnNldCByb3dzIGluIGBhYCB0aGF0IGRvIG5vdCBoYXZlIGEgbWF0Y2ggaW4gYGJgICAgICAgICAgICAgIHwKCiMjIEpvaW5pbmcgRGF0YQoKYGBge3IgYWRkcn0KYWRkciA8LSBkYXRhLmZyYW1lKG5hbWUgPSBjKCJBbGljZSIsICJCb2IiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNhcm9sIiwgImRhdmUiLCAiRXZlIiksCiAgICAgICAgICAgICAgICAgICBlbWFpbCA9IGMoImFsaWNlQGNvbXBhbnkuY29tIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYm9iQGNvbXBhbnkuY29tIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY2Fyb2xAY29tcGFueS5jb20iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJkYXZlQGNvbXBhbnkuY29tIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZXZlQGNvbXBhbnkuY29tIiksCiAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmFkZHIKYGBgCgpgYGB7ciBwaG9uZX0KcGhvbmUgPC0gZGF0YS5mcmFtZShuYW1lID0gYygiQm9iIiwgIkNhcm9sIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRXZlIiwgIkV2ZSIsICJGcmFuayIpLAogICAgICAgICAgICAgICAgICAgIHBob25lID0gYygiOTE5IDU1NS0xMTExIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiOTE5IDU1NS0yMjIyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiOTE5IDU1NS0zMzMzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiMzEwIDU1NS0zMzMzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiOTE5IDU1NS00NDQ0IiksCiAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpwaG9uZQpgYGAKCgojIyBPdXRlciBKb2luCgpJbiBgZHBseXJgOgpgYGB7cn0KZnVsbF9qb2luKGFkZHIsIHBob25lKQpgYGAKCkluIGBiYXNlIFJgOgpgYGB7cn0KbWVyZ2UoYWRkciwgcGhvbmUsIGFsbCA9IFRSVUUpCmBgYAoKIyMgSW5uZXIgSm9pbgoKSW4gYGRwbHlyYDoKYGBge3J9CmlubmVyX2pvaW4oYWRkciwgcGhvbmUpCmBgYAoKSW4gYGJhc2UgUmA6CmBgYHtyfQptZXJnZShhZGRyLCBwaG9uZSwgYWxsID0gRkFMU0UpCmBgYAoKIyMgTGVmdCBKb2luCgpJbiBgZHBseXJgOgpgYGB7cn0KbGVmdF9qb2luKGFkZHIsIHBob25lKQpgYGAKCkluIGBiYXNlIFJgOgpgYGB7cn0KbWVyZ2UoYWRkciwgcGhvbmUsIGFsbC54ID0gVFJVRSkKYGBgCgojIyBSaWdodCBKb2luCgpJbiBgZHBseXJgOgpgYGB7cn0KcmlnaHRfam9pbihhZGRyLCBwaG9uZSkKYGBgCgpJbiBgYmFzZSBSYDoKYGBge3J9Cm1lcmdlKGFkZHIsIHBob25lLCBhbGwueSA9IFRSVUUpCmBgYAoKCiMjIFNlbWkgYW5kIEFudGkgSm9pbnMKCmBgYHtyIHNlbWl9CnNlbWlfam9pbihhZGRyLCBwaG9uZSkgICAjIHNlbWkKYGBgCgpgYGB7ciBhbnRpfQphbnRpX2pvaW4oYWRkciwgcGhvbmUpICAgIyBhbnRpCmBgYAoKCiMjIFNwZWNpYWwgY2FzZTogbWFueS10by1tYW55IHJlbGF0aW9uc2hpcHMKCmBgYHtyfQphZGRyMiA8LSBkYXRhLmZyYW1lKG5hbWUgPSBjKCJBbGljZSIsICJBbGljZSIsICJCb2IiLCAiQm9iIiksCiAgICAgICAgICAgICAgICAgICAgZW1haWw9IGMoImFsaWNlQGNvbXBhbnkuY29tIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYWxpY2VAZ21haWwuY29tIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYm9iQGNvbXBhbnkuY29tIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYm9iQGhvdG1haWwuY29tIiksCiAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpgYGAKCmBgYHtyfQpwaG9uZTIgPC0gZGF0YS5mcmFtZShuYW1lID0gYygiQWxpY2UiLCAiQWxpY2UiLCAiQm9iIiwgIkJvYiIpLAogICAgICAgICAgICAgICAgICAgICBwaG9uZSA9IGMoIjkxOSA1NTUtMTExMSIsICIzMTAgNTU1LTIyMjIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjkxOSA1NTUtMzMzMyIsICIzMTAgNTU1LTMzMzMiKSwKICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpgYGAKCkluIGBkcGx5cmA6CmBgYHtyfQpmdWxsX2pvaW4oYWRkcjIsIHBob25lMiwgYnkgPSAibmFtZSIpCmBgYAoKSW4gYGJhc2UgUmA6CmBgYHtyfQptZXJnZShhZGRyMiwgcGhvbmUyKQpgYGAKCgojIyBFeGFtcGxlOiBFbmhhbmNpbmcgTllDIEZsaWdodCBEYXRhCgpUaGUgYG55Y2ZsaWdodHMxM2AgcGFja2FnZSBhbHNvIGNvbnRhaW5zIGFkZGl0aW9uYWwgaW5mb3JtYXRpb24gYWJvdXQ6Cgp8ICAgRGF0YXNldCAgICAgfCAgIERlc2NyaXB0aW9uICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IC0tLS0tLS0tLS0tLS0gfCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gfAp8IGB3ZWF0aGVyYCAgICAgfCBob3VybHkgbWV0ZXJvbG9naWNhbCBkYXRhIGZvciBlYWNoIGFpcnBvcnQgICAgICAgICAgICAgfAp8IGBwbGFuZXNgICAgICAgfCBjb25zdHJ1Y3Rpb24gaW5mb3JtYXRpb24gYWJvdXQgZWFjaCBwbGFuZSAgICAgICAgICAgICAgfAp8IGBhaXJwb3J0c2AgICAgfCBhaXJwb3J0IG5hbWVzIGFuZCBsb2NhdGlvbnMgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGBhaXJsaW5lc2AgICAgfCB0cmFuc2xhdGlvbiBiZXR3ZWVuIHR3byBsZXR0ZXIgY2FycmllciBjb2RlcyBhbmQgbmFtZXMgfAoKCiMjIFdlYXRoZXIgYW5kIEZsaWdodCBEZWxheXMKCkxldCdzIHRha2UgYSBxdWljayBsb29rIGF0IHRoZSB3ZWF0aGVyIGRhdGEsIHdpdGggYW4gZXllIHRvd2FyZHMgZXhhbWluaW5nIGhvdyBpdCBtaWdodCBhZmZlY3QgdGhpbmdzIGxpa2UgZmxpZ2h0IGRlcGFydHVyZSBkZWxheXMuCgpgYGB7cn0Kd2VhdGhlcgpgYGAKCgojIyBKb2luIGJ5PwoKVmFyaWFibGUgZGVmaW50aW9ucyBpbiB0aGUgYHdlYXRoZXJgIGRhdGFzZXQ6Cgp8ICAgVmFyaWFibGUgICAgfCAgIERlc2NyaXB0aW9uICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IC0tLS0tLS0tLS0tLS0gfCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gfAp8IGBvcmlnaW5gICAgICAgfCBXZWF0aGVyIHN0YXRpb24uIE5hbWVkIG9yaWdpbiB0byBmYWNpbGlhdGUgbWVyZ2luZyB3aXRoIGZsaWdodHMgZGF0YSB8CnwgYHllYXJgLCBgbW9udGhgLCBgZGF5YCwgYGhvdXJgIHwgVGltZSBvZiByZWNvcmRpbmcgICAgICAgICB8CnwgYHRlbXBgLCBgZGV3cGAgfCBUZW1wZXJhdHVyZSBhbmQgZGV3cG9pbnQgaW4gRmFocmVuaGVpdCAgICB8CnwgYGh1bWlkYCAgICAgIHwgUmVsYXRpdmUgaHVtaWRpdHkgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgYHdpbmRfZGlyYCwgYHdpbmRfc3BlZWRgLCBgd2luZF9ndXN0YCB8IFdpbmQgZGlyZWN0aW9uIChpbiBkZWdyZWVzKSwgc3BlZWQgKGluIG1waCksIGFuZCBndXN0IHNwZWVkIChpbiBtcGgpIHwKfCBgcHJlY2lwYCAgICAgfCBQcmVjaXB0YXRpb24sIGluIGluY2hlcyAgICAgICAgICAgICAgICAgICAgIHwKfCBgcHJlc3N1cmVgICAgfCBTZWEgbGV2ZWwgcHJlc3N1cmUgaW4gbWlsbGliYXJzICAgICAgICAgICAgIHwKfCBgdmlzaWJgICAgICAgfCBWaXNpYmlsaXR5IGluIG1pbGVzICAgICAgICAgICAgICAgICAgICAgICAgIHwKCgo8IS0tCiogdGFpbG51bSAtIFRhaWwgbnVtYmVyCiogeWVhciAtIFllYXIgbWFudWZhY3R1cmVkCiogdHlwZSAtIFR5cGUgb2YgcGxhbmUKKiBtYW51ZmFjdHVyZXIgLSBNYW51ZmFjdHVyZXIKKiBtb2RlbCAtIE1vZGVsCiogZW5naW5lcyAtIE51bWJlciBvZiBlbmdpbmVzCiogc2VhdHMgLSBOdW1iZXIgb2Ygc2VhdHMKKiBzcGVlZCAtIEF2ZXJhZ2UgY3J1aXNpbmcgc3BlZWQgaW4gbXBoCiogZW5naW5lIC0gVHlwZSBvZiBlbmdpbmUKLS0+CgpgYGB7cn0KaW50ZXJzZWN0KG5hbWVzKHdlYXRoZXIpLCBuYW1lcyhmbGlnaHRzKSkKYGBgCgoKCgojIyBKb2luaW5nIGZsaWdodHMgYW5kIHdlYXRoZXIKCldoZW4gam9pbmluZyB0aGVzZSB0d28gZGF0YSBmcmFtZXMgSSB3aWxsIHVzZSBhbiBpbm5lciBqb2luIChiZWNhdXNlIEkgb25seSB3YW50IHRoZSBmbGlnaHRzIHRoYXQgaGF2ZSB0aGUgd2VhdGhlciBkYXRhLCBhbmQgSSBvbmx5IHdhbnQgd2VhdGhlciBkYXRhIHdoZW4gdGhlcmUgd2FzIGEgZmxpZ2h0KQoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0KKGZsaWdodHN3IDwtIGlubmVyX2pvaW4oZmxpZ2h0cywgd2VhdGhlcikpCmBgYAoKTGV0J3MgbWFrZSBhIGBnZ3Bsb3RgIG9mIHRoZSBkZWxheSB0aW1lIHZzLiB2aXNpYmlsaXR5OgoKYGBge3J9CmZsaWdodHN3ICU+JQogIGdncGxvdChhZXMoeCA9IHZpc2liLCB5ID0gZGVwX2RlbGF5KSkgKwogIGdlb21fcG9pbnQoKQpgYGAKCgoKTGV0J3MgdGFrZSBhIGNsb3NlciBsb29rIGF0IHRoZSBkZXBlbmRlbnQgdmFyaWFibGU6IHZpc2liaWxpdHk6CgpgYGB7cn0KZmxpZ2h0c3cgJT4lCiAgZ2dwbG90KGFlcyh4ID0gdmlzaWIpKSArCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJibHVlIikKdGFibGUoZmxpZ2h0c3ckdmlzaWIpCmBgYAoKCldvdyEgVGhlIHZhc3QgbWFqb3JpdHkgb2YgZmxpZ2h0cyBhcmUgdW5kZXIgY2xlYXIgc2tpZXMgd2l0aCBgPiAxMGAgbWlsZXMgdmlzaWJpbGl0eS4gR29vZCB0byBrbm93LgoKYGBge3J9CmZsaWdodHN3ICU+JQogIGdncGxvdChhZXMoeCA9IHZpc2liLCB5ID0gZGVwX2RlbGF5KSkgKwogIGdlb21faml0dGVyKHNoYXBlID0gMjEsIGNvbG9yID0gImJsdWUiLCBmaWxsID0gImdyYXkiLCBzaXplID0gMS41LCB3aWR0aCA9IDAuMikgKwogIGdlb21fc21vb3RoKGNvbG9yID0gJ3JlZCcpCmBgYAoKClRoZXJlIGFyZSBtYW55ICpuZWdhdGl2ZSogZGVwYXJ0dXJlIHRpbWUgdmFsdWVzIChlYXJseSEpLCB3aGljaCBzdG9wIHVzIGZyb20gcGxvdHRpbmcgdGhlIHktYXhpcyBvbiBhIGxvZy1zY2FsZSAod2hpY2ggbWlnaHQgYmUgbW9yZSB2aXN1YWxseSBhcHBlYWxpbmcpIC4uLiBsZXQncyBib3hwbG90IGJ1dCByZW1vdmUgdGhlbSBmaXJzdDoKCmBgYHtyfQpmbGlnaHRzdyAlPiUKICBmaWx0ZXIoZGVwX2RlbGF5ID4gMCkgICU+JSAKICBtdXRhdGUodmlzaWIgJTw+JSBmYWN0b3IpICU+JQogIGdncGxvdChhZXMoeCA9IHZpc2liLCB5ID0gZGVwX2RlbGF5LCBncm91cCA9IHZpc2liKSkgKwogIGdlb21fYm94cGxvdChmaWxsID0gImJsdWUiLCBjb2xvciA9ICJncmF5IikgKwogIGNvb3JkX3RyYW5zKHkgPSAibG9nMTAiKSArCiAgeGxhYigiVmlzaWJpbGl0eSIpICsKICB5bGFiKCJEZXBhcnR1cmUgRGVsYXkiKQpgYGAKCgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgoKIyBNb3JlIEV4ZXJjaXNlcwoKMS4gQ2hlY2sgc29tZSBvZiB0aGUgb3RoZXIgd2VhdGhlciB2YXJpYWJsZXMgKGUuZy4gYHRlbXBgLCBgd2luZF9zcGVlZGAsIGBldGNgKSBhbmQgc2VlIGlmIHlvdSBjYW4gZmluZCBhbnkgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlbSBhbmQgZGVwYXJ0dXJlIGRlbGF5IChgZGVwX2RlbGF5YCkuCgoyLiBNZXJnZSB0aGUgYGZsaWdodHNgIGRhdGEgd2l0aCB0aGUgYHBsYW5lc2AgZGF0YSBzZXQgKHBheSBhdHRlbnRpb24gdG8gd2hhdCBjb2x1bW5zIGFyZSBiZWluZyB1c2VkIGZvciB0aGUgam9pbikuIEFyZSBvbGRlciBwbGFuZXMgbW9yZSBsaWtlbHkgdG8gYmUgZGVsYXllZD8gV2hhdCBhYm91dCBwbGFuZXMgZnJvbSBBaXJidXMgdnMgQm9laW5nIHZzIEVtYnJhZXI/CgoKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgoKIyBBY2tub3dsZWRnbWVudHMKCkFib3ZlIG1hdGVyaWFscyBhcmUgZGVyaXZlZCBpbiBwYXJ0IGZyb20gdGhlIGZvbGxvd2luZyBzb3VyY2VzOgoKKiBbUlN0dWRpbyBEYXRhIFdyYW5nbGluZyBDaGVhdCBTaGVldF0oaHR0cDovL3d3dy5yc3R1ZGlvLmNvbS93cC1jb250ZW50L3VwbG9hZHMvMjAxNS8wMi9kYXRhLXdyYW5nbGluZy1jaGVhdHNoZWV0LnBkZikKKiBQYWNrYWdlIFZpZ25ldHRlczoKICAgICsgW2RwbHlyXShodHRwOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9kcGx5ci92aWduZXR0ZXMvaW50cm9kdWN0aW9uLmh0bWwpCiAgICArIFt0aWR5cl0oaHR0cDovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvdGlkeXIvdmlnbmV0dGVzL3RpZHktZGF0YS5odG1sKQoqIFtVc2VSIDIwMTQ6IGRwbHlyIFR1dG9yaWFsXShodHRwczovL3d3dy5kcm9wYm94LmNvbS9zaC9pOHFubHV3bXVpZWljeGMvQUFBZ3Q5dElLb0ltN1daS0l5SzI1bGg2YSkKCg==